package parseutil

import (
	"bytes"
	"fmt"
	"github.com/henrylee2cn/aster/aster"
	"github.com/henrylee2cn/goutil"
	"io/ioutil"
	"log"
	"strings"
	//"html"
	"text/template"
)

//import (
//	"embed"
//	"fmt"
//)
//
const tplContent = `package {{.packageName}}

import (
	"database/sql"
	"fmt"
	"log"
	"reflect"
	"strings"
)

type (
	{{.modelName}}Repo struct {
		db      *sql.DB
		OpenLog bool
	}
)

func (w *{{.modelName}}Repo) QueryById(input *{{.modelName}}) (u *{{.modelName}}, err error) {
	var db = w.db
	var sql = "select {{.selectFields}} from {{.tableName}}  where  {{.tableId}} = ?"
	stmt, err := db.Prepare(sql)
	if err != nil {
		return u, err
	}

	defer stmt.Close()
	if w.OpenLog {
		log.Println("sql = ", sql)
		log.Println("params = ", input.{{.tableIdProp}})
	}
	row := stmt.QueryRow(input.{{.tableIdProp}})

	u = &{{.modelName}}{}
	_ = row.Scan({{.scanFields}})

	return u, err
}

func (w *{{.modelName}}Repo) QueryAll() (res []*{{.modelName}}, err error) {
	var db = w.db
	//qq := &{{.modelName}}{}
	var sql = "select {{.selectFields}}  from {{.tableName}} "

	stmt, err := db.Prepare(sql)
	if err != nil {
		return res, err
	}
	defer stmt.Close()
	if w.OpenLog {
		log.Println("sql = ", sql)
		//log.Println("params = ",)
	}

	rows, err := stmt.Query()
	if err != nil {
		return res, err
	}
	defer rows.Close()

	for rows.Next() {
		var u {{.modelName}}
		_ = rows.Scan({{.scanFields}})
		res = append(res, &u)
	}
	return res, err
}

func (w *{{.modelName}}Repo) DeleteById(input *{{.modelName}}) (sql.Result, error) {
	var db = w.db
	var sql = "delete from {{.tableName}}  where {{.tableId}}= ?"
	if w.OpenLog {
		log.Println(sql)
	}
	stmt, err := db.Prepare(sql)
	if err != nil {
		return nil, err
	}
	defer stmt.Close()
	exec, err := stmt.Exec(input.{{.tableIdProp}} )
	return exec, err
}
func (w *{{.modelName}}Repo) UpdateById(u *{{.modelName}}) (sql.Result, error) {
	var db = w.db
	var x = "update {{.tableName}}   set {{.setFields}}  where {{.tableId}} = ?"
	if w.OpenLog {
		log.Println(x)
	}
	stmt, err := db.Prepare(x)
	if err != nil {
		return nil, err
	}
	defer stmt.Close()
	exec, err := stmt.Exec(u.{{.tableIdProp}})
	return exec, err
}
func (w *{{.modelName}}Repo) UpdateOmitZeroValue(u *{{.modelName}}, where string, args ...interface{}) (sql.Result, error) {
	var db = w.db
	var sql strings.Builder
	sql.WriteString("update {{.tableName}}  set ")
	var setCnt int
	rvalue := reflect.Indirect(reflect.ValueOf(u))
	valueType := reflect.TypeOf(u)
	var params []interface{}
	for i := 0; i < rvalue.NumField(); i++ {
		var cur = rvalue.Field(i)
		if cur.IsValid() && cur.IsZero() == false {
			setCnt++
			field := valueType.Elem().Field(i)
			var name = field.Tag.Get("db")
			if name == "" {
				name = field.Name
			}
			sql.WriteString(fmt.Sprintf("%s = ?", name))
			params = append(params, cur.Interface())
		}
	}
	if setCnt == 0 {
		return nil, nil
	}
	sql.WriteString(" where ")
	sql.WriteString(where)
	params = append(params, args...)
	if w.OpenLog {
		fmt.Println(sql.String())
		fmt.Println(params)

	}

	stmt, err := db.Prepare(sql.String())
	if err != nil {
		return nil, err
	}
	defer stmt.Close()
	if w.OpenLog {
		log.Println("sql = ", sql)
		log.Println("params = ", u)
	}
	return stmt.Exec(params...)

}

func (w *{{.modelName}}Repo) Insert(u *{{.modelName}}) (sql.Result, error) {
	var db = w.db
	var sql = "insert into {{.tableName}}  ({{.selectFields}}) values ({{.questionFields}})"
	if w.OpenLog {
		log.Println("sql = ", sql)
		log.Println("params = ", u)
	}
	stmt, err := db.Prepare(sql)
	if err != nil {
		return nil, err
	}
	defer stmt.Close()
	return stmt.Exec({{.scanFields}})

}

func (w *{{.modelName}}Repo) InsertSelective(u *{{.modelName}}) (sql.Result, error) {
	//var db = w.db
	var sqlCmd = strings.Builder{}
	sqlCmd.WriteString("insert into {{.tableName}} (")
	var setCnt int
	rvalue := reflect.Indirect(reflect.ValueOf(u))
	valueType := reflect.TypeOf(u)
	var params []interface{}
	for i := 0; i < rvalue.NumField(); i++ {
		var cur = rvalue.Field(i)
		if cur.IsValid() && cur.IsZero() == false {
			if setCnt > 0 {
				sqlCmd.WriteString(",")
			}
			setCnt++
			field := valueType.Elem().Field(i)
			var name = field.Tag.Get("db")
			if name == "" {
				name = field.Name
			}

			sqlCmd.WriteString(fmt.Sprintf("%s", name))
			params = append(params, cur.Interface())
		}
	}

	if setCnt == 0 {
		return nil, nil
	}
	sqlCmd.WriteString(")")
	sqlCmd.WriteString(" values (")
	for i := 0; i < setCnt; i++ {
		if i > 0 && i < setCnt {
			sqlCmd.WriteString(",")
		}
		sqlCmd.WriteString("?")

	}
	sqlCmd.WriteString(") ")

	if w.OpenLog {
		log.Println("sqlCmd = ", sqlCmd.String())
		log.Printf("params = %+v\n", u)
	}
	var db = w.db
	stmt, err := db.Prepare(sqlCmd.String())
	if err != nil {
		return nil, err
	}
	defer stmt.Close()
	return stmt.Exec(params...)

}
func (w *{{.modelName}}Repo) QueryCallback(sqlStr string, callback func(rows *sql.Rows) (*{{.modelName}}, error), args ...interface{}) (res []*{{.modelName}}, err error) {
	var db = w.db
	stmt, err := db.Prepare(sqlStr)
	if err != nil {
		return res, err
	}
	defer stmt.Close()
	if w.OpenLog {
		log.Println("sql = ", sqlStr)
		log.Println("params = ", args)
	}
	//args0 := args
	rows, err := stmt.Query(args...)
	if err != nil {
		return res, err
	}
	defer rows.Close()
	for rows.Next() {
		{{.modelName}}, err := callback(rows)
		if err != nil {
			return res, err
		}
		res = append(res, {{.modelName}})
	}
	return res, err
}
func (w *{{.modelName}}Repo)  Exec(sqlcmd string ,args...interface{}) (sql.Result,error) {
	var db = w.db
	var stmt , err = db.Prepare(sqlcmd)
	if err != nil {
		return nil,err
	}
	defer stmt.Close()
	exec, err := stmt.Exec(args...)
	return exec,err
}


func (w *DeptRepo) Query(sqlCmd string,calback func(stmt *sql.Stmt) error ) (error) {
	stmt, err := w.db.Prepare(sqlCmd)
	if err != nil {
		return err
	}
	defer stmt.Close()
	err = calback(stmt)
	return err
}

`

func OutPutDao(program *aster.Program, pkgName string, modelName string, pkgDir string) {
	//var (
	//	tableId ,scanFields,selectFields,tableName,setFields string
	//)
	modelName = strings.Trim(modelName, " ")
	//fmt.Println( program.Format())
	modelPkg := program.InitialPackages()[0].Pkg
	fmt.Println(modelPkg.Path())
	var (
		tableId, tableName string
		tableIdProp        string
		dbCol, props       []string
		selectFields       string
		scanFields         string
		setFields          string
		questionFields     string

	)
	program.Inspect(func(fa aster.Facade) bool {
		if fa.TypKind() != aster.Struct {
			return true
		}
		if fa.Name() != modelName {
			return true
		}
		fmt.Println("struct type ")

		var s = strings.Split(fa.Doc(), "\n")
		for _, line := range s {
			//fmt.Println("line = ",line)
			if strings.Contains(line, "@tableName") {
				line = strings.Replace(line, "@tableName", "", -1)
				line = strings.ReplaceAll(line, "", "")
				line = strings.ReplaceAll(line, ":", "")
				tableName = strings.Trim(line, " ")

			} else if strings.Contains(line, "@tableId") {
				line = strings.Replace(line, "@tableId", "", -1)
				line = strings.ReplaceAll(line, "", "")
				line = strings.ReplaceAll(line, ":", "")
				tableId = strings.Trim(line, " ")
			}

		}

		for i := 0; i < fa.NumFields(); i++ {
			f := fa.Field(i)

			var tag, _ = f.Tags().Get("db")

			opt := tag.Name
			if opt == "-" {
				continue
			}
			fmt.Println(opt, tableId)
			if tag.Name == tableId || f.Name() == tableId {
				tableIdProp = f.Name()
				fmt.Println("tableId prop = ", tableIdProp)
			}
			dbCol = append(dbCol, opt)
			props = append(props, f.Name())
		}
		return true
	})
	if len(dbCol) <= 0 {
		fmt.Println("exit status 1")
		return
	}
	selectFields = strings.Join(dbCol, ",")
	for i := 0; i < len(props); i++ {
		props[i] = "&u." + props[i]
	}
	scanFields = strings.Join(props, ",")
	// =================== scanfields ====================
	for i := 0; i < len(dbCol); i++ {
		dbCol[i] = fmt.Sprintf(" %s = ? ", dbCol[i])
	}
	setFields = strings.Join(dbCol, ",")
	//=========================
	for i := 0; i < len(dbCol); i++ {
		dbCol[i] = "?"
	}
	questionFields = strings.Join(dbCol, ",")

	var (
		buf bytes.Buffer
	)
	//if packageName[0]>='A' && packageName[0]<='Z' {
	//	var p0 = packageName[0] ^ 32
	//	var s = []byte(packageName)
	//	s[0] = p0
	//	packageName = string(s)
	//
	//
	//}
	var tpl, err = template.New("xxoo").Parse(tplContent)
	if err != nil {
		fmt.Println(err)

		return
	}
	//if pkgName == "" {
	//	pkgName =
	//}
	//tpl.Option()
	err = tpl.Execute(&buf, map[string]interface{}{
		"modelName":   modelName,
		"tableName":   tableName,
		"tableIdProp": tableIdProp,

		"tableId": tableId,
		//"tableName":   tableName,
		"setFields":    setFields,
		"scanFields":   scanFields,
		"packageName":  pkgName,
		"selectFields": selectFields,
		"questionFields":     questionFields,
		"modelPkg":modelPkg,
	})
	if err != nil {
		log.Println(err)
	}
	if pkgDir == "" {
		pkgDir = "./"
	}
	var pkgFile = fmt.Sprintf("%s/%s_gen_repo.go", pkgDir, goutil.SnakeString(modelName))
	//fmt.Println(buf.String())
	//var s = buf.String()
	//res := template.HTMLEscaper(s)
	err = ioutil.WriteFile(pkgFile, buf.Bytes(), 0644)
	if err != nil {
		panic(err)
	}
}

func ResetTags(program *aster.Program) {
	program.Inspect(func(fa aster.Facade) bool {

		if fa.TypKind() != aster.Struct {
			return true
		}
		log.Println("type", fa.Name())
		for i := fa.NumFields() - 1; i >= 0; i-- {
			field := fa.Field(i)
			if !field.Exported() {
				continue
			}
			var name = goutil.CamelString(field.Name())
			if name[0] >= 'A' && name[0] <= 'Z' {
				var bs = []byte(name)
				bs[0] ^= 32
				name = string(bs)
			}
			field.Tags().Set(&aster.Tag{
				Key:  "json",
				Name: name,
				//Options: []string{"omitempty"},
			})
			field.Tags().Set(&aster.Tag{
				Key:  "form",
				Name: name,
				//Options: []string{"omitempty"},
			})
			field.Tags().Set(&aster.Tag{
				Key:  "db",
				Name: goutil.SnakeString(name),
			})
		}
		return true
	})
	program.Rewrite()
}

func ParseFileName(sourceFile string, outDir, pkgName string, modelName string) {
	file, err := ioutil.ReadFile(sourceFile)
	if err != nil {
		log.Println(err)
		panic(err)
	}
	var content = string(file)
	//fmt.Println("content = ",content)
	program, err := aster.LoadFile(sourceFile, content)
	if err != nil {
		log.Println(err)
		panic(err)
	}

	ResetTags(program)

	program, err = aster.LoadFile(sourceFile, content)
	if err != nil {
		panic(err)
	}
	OutPutDao(program, pkgName, modelName, outDir)

}
